バックグラウンドで JSON を解析する
デフォルトでは、Dart アプリはすべての作業を単一のスレッドで実行します。 多くの場合、このモデルはコーディングを簡素化し、十分に高速です アプリのパフォーマンスの低下やアニメーションの途切れが発生しないこと、 よく「ジャンク」と呼ばれます。
ただし、コストのかかる計算を実行する必要がある場合があります。 非常に大きな JSON ドキュメントの解析など。 この作業に 16 ミリ秒以上かかる場合は、 ユーザーがジャンクを経験します。
ジャンクを回避するには、負荷の高い計算を実行する必要があります 背景ではこんな感じ。 Android では、これは別のスレッドで作業をスケジュールすることを意味します。 Flutter では、別の隔離する。 このレシピでは次の手順を使用します。
- を追加します。
http
パッケージ。 - を使用してネットワーク リクエストを実行します。
http
パッケージ。 - 応答を写真のリストに変換します。
- この作業を別の単離物に移動します。
http
パッケージ
1.まず、http
プロジェクトにパッケージ化します。
のhttp
パッケージによりネットワークの実行が容易になります
JSON エンドポイントからのデータのフェッチなどのリクエスト。
追加するには、http
パッケージを依存関係として、
走るflutter pub add
:
$ flutter pub add http
2. ネットワークリクエストを行う
この例では、大きな JSON ドキュメントをフェッチする方法について説明します。
これには、JSONプレースホルダーREST API、
を使用してhttp.get()
方法。
Future<http.Response> fetchPhotos(http.Client client) async {
return client.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
}
3. JSON を解析して写真のリストに変換します
次に、担当者の指導に従って、インターネットからデータを取得するレシピ、
を変換しますhttp.Response
Dart オブジェクトのリストに追加します。
これにより、データの操作が容易になります。
Photo
クラス
を作成しますまず、Photo
写真に関するデータを含むクラス。
を含めるfromJson()
ファクトリメソッドを使用すると、Photo
JSON オブジェクトから始まります。
class Photo {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
const Photo({
required this.albumId,
required this.id,
required this.title,
required this.url,
required this.thumbnailUrl,
});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
albumId: json['albumId'] as int,
id: json['id'] as int,
title: json['title'] as String,
url: json['url'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
応答を写真のリストに変換します
次に、次の手順に従って、fetchPhotos()
を返す関数Future<List<Photo>>
:
- を作成します
parsePhotos()
応答を変換する関数 体をList<Photo>
。 - 使用
parsePhotos()
の機能fetchPhotos()
関数。
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client
.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
// Use the compute function to run parsePhotos in a separate isolate.
return parsePhotos(response.body);
}
4. この作業を別の分離に移動します
を実行すると、fetchPhotos()
遅いデバイスでは機能しませんが、
解析中にアプリが短時間フリーズすることに気づくかもしれません。
JSONを変換します。これはジャンクなので、削除する必要があります。
解析と変換を移動することでジャンクを削除できます
を使用して背景を分離しますcompute()
Flutterが提供する関数です。のcompute()
関数の実行コストが高い
バックグラウンドの関数は分離して結果を返します。この場合、
を実行しますparsePhotos()
バックグラウンドでの機能。
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client
.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
分離株の操作に関する注意事項
アイソレータはメッセージをやり取りすることによって通信します。これらのメッセージは、
などのプリミティブな値にするnull
、num
、bool
、double
、 またString
、 また
などの単純なオブジェクトList<Photo>
この例では。
より複雑なオブジェクトを渡そうとすると、エラーが発生する可能性があります。
などFuture
またhttp.Response
分離株の間。
別の解決策として、以下を確認してください。worker_manager
またworkmanager
バックグラウンド処理用のパッケージ。
完全な例
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client
.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
class Photo {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
const Photo({
required this.albumId,
required this.id,
required this.title,
required this.url,
required this.thumbnailUrl,
});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
albumId: json['albumId'] as int,
id: json['id'] as int,
title: json['title'] as String,
url: json['url'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const appTitle = 'Isolate Demo';
return const MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: FutureBuilder<List<Photo>>(
future: fetchPhotos(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('An error has occurred!'),
);
} else if (snapshot.hasData) {
return PhotosList(photos: snapshot.data!);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
class PhotosList extends StatelessWidget {
const PhotosList({super.key, required this.photos});
final List<Photo> photos;
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: photos.length,
itemBuilder: (context, index) {
return Image.network(photos[index].thumbnailUrl);
},
);
}
}